データはbufにコピーされる
• ioctl(sockfd, FIONREAD , &nbytes);
使えるOSは限られる(Linuxでは使える)
write()
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
unsigned char buf[4];
ssize_t n;
buf[0] = 0x5a;
buf[1] = 0x5b;
buf[2] = 0x5c;
buf[3] = 0x5b;
if (write(sockfd, buf, 4) == -1) { perror("write error");
exit(1);
}
ソケットセンドバッファに余裕がないときにはブ ロックする(エラーにはならない)。
ブロックしないようにするにはノンブロックキグ ソケットオプションを使う(ノンブロッキングにす るとエラー処理とかでだいぶ行数が増える)。
socket send/receive buffer の大きさ
application
TCP
IP
datalink
application buffer write()
socket send buffer
user process kernel
socket receive buffer
application buffer
read()
socket send/receive buffer の大きさの調整
•
受信に関してはLinux
では自動調節機能がある•
多重読み出しを行うときにはあらかじめ大きくしてお く必要があるecho 0 > /proc/sys/net/ipv4/tcp_timestamps
echo 1 > /proc/sys/net/ipv4/tcp_moderate_rcvbuf echo 4194304 > /proc/sys/net/core/wmem_max
echo 4194304 > /proc/sys/net/core/rmem_max
echo 4194304 > /proc/sys/net/core/wmem_default echo 4194304 > /proc/sys/net/core/rmem_default
echo 4096 131072 4194304 > /proc/sys/net/ipv4/tcp_rmem echo 4096 131072 4194304 > /proc/sys/net/ipv4/tcp_wmem
テスト環境、テスト方法 (2)
• PC(HP xw8600) 1
台– Quad-Core Xeon (2.5GHz) x 2
(のうち4core
を使用)– 2GB memory – BroadCom GbE
– Hitachi SATA Disk (7200rpm, 32MB
バッファ, 1TB) – OS: RedHat Enterprise Linux 5.2
•
ネットワークスイッチ– Cisco Catalyst 2960G-24TC-L x 2, – Cisco Catalyst 2960G-8TC-L
up to 30
Daq Operator
ネットワークからのデータ読み取り
MB/s
8 24 40
• 各種パラメータはOS デフォルト
• Loggerはディスクに データをセーブしない 状態で稼動
• Linux
のソケットレシーブバッファの大きさはデ フォルトでは自動調節が有効になっている。• socket()
直後の初期値は/proc/sys/net/ipv4/tcp_rmem
にある2番目の 数値で約86kB
•
ソケットレシーブバッファの初期値を大きくす ることにより全データ読めるようになった。ネットワークパラメータの調整
% cat /proc/sys/net/ipv4/tcp_rmem
4096 87380 4194304
ネットワークパラメータ調整後
データのセーブなしの場合。
読み取りが順調に行えている
40
8 24
MB/s
ここまでのまとめ
•
ソケットファイルディスクリプタを取得するとあ とは通常のファイルの読み書きと同様•
ファイルを読むときとは違って指定したサイズ が必ずしも読めるとは限らない。指定したサ イズ必ず読みたければそのような関数を作る 必要がある。ネットワークバイトオーダー (1)
• unsigned char buf[10];
アドレスは
buf[0], buf[1], buf[2]
の順に大きくなる• unsigned char buf[10];
write(sockfd, buf, 10)
;とすると
buf[0], buf[1], buf[2] …
の順に送られる。• read(sockfd, buf, 10);
着た順に
buf[0], buf[1], buf[2]
に格納される。ネットワークバイトオーダー (2)
// intがどういう順番でメモリーに
// 入っているか調べるプログラム
#include <stdio.h>
int main(int argc, char *argv[]) {
int i;
union num_tag {
unsigned char c[sizeof(int)];
unsigned int num;
} u_num;
u_num.num = 0x01020304;
for (i = 0; i < sizeof(int); i++) {
printf("u_num.c[%d]: %p 0x%02x ¥n", i, &u_num.c[i], u_num.c[i]);
}
return 0;
}
出力 (i386)
u_num.c[0]: 0xbfbfe850 0x04 u_num.c[1]: 0xbfbfe851 0x03 u_num.c[2]: 0xbfbfe852 0x02 u_num.c[3]: 0xbfbfe853 0x01
ネットワークバイトオーダー (3)
big endian
では 0x 01020304 = 16909060little endian
では 0x 04030201 = 67305985 ネットワークバイトオーダーはbig endian
0x 01 02 03 04 の順に送られてきたデータをread(sockfd, buf, 4)で読んだ場合
0x01 0x02 0x03 0x04 0x01 0x02 0x03 0x04
big endian little endian
buf buf
ネットワークバイトオーダー (4)
•
ホストオーダー⇔ネットワークバイトオーダー 変換関数– htonl (host to network long)
– htons (host to network short)
– ntohl (network to host long)
– ntohs (network to host short)
daytime client (1)
• xinetd
内蔵サーバーdaytime (port 13)
• /etc/xinetd.d/daytime-stream
にてdisable = no
に変更して
service xinetd restart
• telnet localhost 13
すると現在日時が表示される
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAXLINE 1024
int main(int argc, char *argv[]) {
unsigned char line[MAXLINE + 1];
struct sockaddr_in servaddr;
char *ip_address = "127.0.0.1";
int port = 13;
int sockfd, n;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
n = inet_pton(AF_INET, ip_address, &servaddr.sin_addr);
if (n < 0) {
perror("inet_pton");
exit(1);
}
else if (n == 0) {
fprintf(stderr, "invalid address %s", ip_address);
exit(1);
}
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket");
exit(1);
}
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { perror("connect");
exit(1);
}
for ( ; ; ) {
n = read(sockfd, line, MAXLINE);
if (n < 0) {
perror("read");
exit(1);
}
else if (n == 0) { printf("EOF¥n");
break;
}
line[n] = '¥0'; /* string termination */
printf("%s¥n", line);
}
if (close(sockfd) < 0) { perror("close");
exit(1);
}
return 0;
}
0123456789012345678901234 5 6 07 AUG 2012 13:02:10 JST¥r¥n¥0
[daq@localhost daytimeclient]$ ./daytimeclient | hexdump -vC
00000000 30 37 20 41 55 47 20 32 30 31 32 20 31 33 3a 30 |07 AUG 2012 13:0|
00000010 32 3a 31 30 20 4a 53 54 0d 0a 0a 45 4f 46 0a |2:10 JST...EOF.|
情報のありか
• Manual Page
•
本Manual Pages
•
セクション– 1 (Utility Program) – 2 (System call)
– 3 (Library) – 4 (Device)
– 5 (File format) – 6 (Game)
– 7 (Misc.)
– 8 (Administration)
Linux
だとこの他– 3P (Posix)
Manual Pages
• man
コマンド• Linux
のマニュアルページは– http://www.kernel.org/doc/man-pages/
–
最新のマニュアルはここで読める。–
利用しているkernel
、library
等のバージョンに注 意する必要がある。Manual Pages
• Header
READ(3P) POSIX Programmer's Manual READ(3P) READ(2) Linux Programmer's Manual READ(2)
• SYNOPSIS
• DESCRIPTION
• RETURN VALUE
• SEE ALSO
• EXAMPLE
Manual Pages(
例題)
READ(2) Linux Programmer's Manual READ(2) NAME
read - read from a file descriptor SYNOPSIS
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
DESCRIPTION
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
:
RETURN VALUE
:
ERRORS
:
CONFORMING TO
SVr4, 4.3BSD, POSIX.1-2001.
NOTES
Manual Pages(
例題)
READ(2) Linux Programmer's Manual READ(2) NAME
read - read from a file descriptor SYNOPSIS
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
DESCRIPTION
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
:
RETURN VALUE
:
ERRORS
:
CONFORMING TO
SVr4, 4.3BSD, POSIX.1-2001.
NOTES
:
Utility
• gettimeofday()
• nc
• tcpdump
、wireshark (ex. ethereal)
gettimeofday() で現在時刻の取得
struct timeval start, end, diff;
if (gettimeofday(&start, NULL) < 0) { err(1, "gettimeofday");
}
/* ... */
if (getimeofday(&end, NULL) < 0) { err(1, "gettimeofday");
}
/* 時間差をとるには引き算してもよいし、timersub()関数を使ってもよい timersub(&end, &start, &diff);
printf("%ld.%06ld¥n", result.tv_sec, result.tv_usec);
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
Linuxではgettimeofday()を1,000,000回繰り返して1秒以下(CPUに依存する)
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
ナノ秒まで必要なとき
clock_gettime(CLOCK_REALTIME, &ts);
コンパイル時に-lrtが必要 struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
余談: 最近のファイルシステムのタイムスタンプはナノ秒まで記録されている
% touch X
% ls -l --full X
-rw-rw-r-- 1 sendai sendai 0 2012-08-02 15:02:55.362116699 +0900 X
nc (netcat)
• nc - arbitrary TCP and UDP connections and listens
• nc 192.168.0.16 > datafile
で接続してデータを とってみる• nc 192.168.0.16 | tee log.dat | prog_histo
% nc -l 1234
(これで待機して別の端末から) Hello, world
% nc 127.0.0.1 1234 Hello, world
tcpdump
•
ネットワーク上を流れているパケットを見るコマンド–
接続できないんだけどパケットはでているのか?–
データが読めないんだけど向こうからパケットは きているんでしょうか?• root
にならないと使えない•
起動方法# tcpdump -n -w dumpfile -i eth0 # tcpdump -n -r dumpfile
• Selector
# tcpdump -n -r host 192.168.0.16
# tcpdump -n -r src 192.168.0.16 and dst 192.168.0.17
tcpdump 出力例
TCP
の3way
ハンドシェイク付近:11:27:55.137827 IP 192.168.0.16.59448 > 192.168.0.17.http: S 153443204:
153443204(0) win 5840 <mss 1460,sackOK,timestamp 587094474 0,nop,wscale 7>
11:27:55.139573 IP 192.168.0.17.http > 192.168.0.16.59448: S 4091282933:
4091282933(0) ack 153443205 win 65535 <mss 1460,nop,wscale 1,nop,nop,timestamp 3029380287 587094474,sackOK,eol>
11:27:55.139591 IP 192.168.0.16.59448 > 192.168.0.17.http: . ack 1 win 46
<nop,nop,timestamp 587094479 3029380287>
11:27:55.139751 IP 192.168.0.16.59448 > 192.168.0.17.http: P 1:103(102) ack 1 win 46 <nop,nop,timestamp 587094479 3029380287>
11:27:55.143520 IP 192.168.0.17.http > 192.168.0.16.59448: P 1:252(251) ack103 win 33304 <nop,nop,timestamp 3029380290 587094479>
tcpdump - 時刻情報
•
絶対時刻ではなくて相対的な時間に変換する プログラムを作っておくと便利なことがある。0.000000 0.000000 IP 192.168.0.16.59448 > 192.168.0.17.http: S 153443204:1534432 0.001746 0.001746 IP 192.168.0.17.http > 192.168.0.16.59448: S 4091282933:409128 0.001764 0.000018 IP 192.168.0.16.59448 > 192.168.0.17.http: . ack 1 win 46 <nop 0.001924 0.000160 IP 192.168.0.16.59448 > 192.168.0.17.http: P 1:103(102) ack 1 0.005693 0.003769 IP 192.168.0.17.http > 192.168.0.16.59448: P 1:252(251) ack 10 0.005703 0.000010 IP 192.168.0.16.59448 > 192.168.0.17.http: . ack 252 win 54 <n 1.107822 1.102119 IP 192.168.0.16.59448 > 192.168.0.17.http: F 103:103(0) ack 25 1.108482 0.000660 IP 192.168.0.17.http > 192.168.0.16.59448: . ack 104 win 33304 1.109608 0.001126 IP 192.168.0.17.http > 192.168.0.16.59448: F 252:252(0) ack 10 1.109618 0.000010 IP 192.168.0.16.59448 > 192.168.0.17.http: . ack 253 win 54 <n
最初の欄はSYNを送ってからの経過時間
2番目の欄は直前の行との時間差を示すもの
tcpdump + program log
• tcpdump
の時刻情報と同じ時刻フォーマットでログを出すようにしておいて
tcpdump
をとりつつプ ログラムを走らせあとからマージする:(tcpdump -n -r tcpdump.out; cat log) | sort -n
NEUNET Protocol
クライアント サーバー(検出器モジュール)
length request
length + data
length request
length + data
tcpdump + program log
0.000000 0.000000 connect start
0.000063 0.000063 IP 192.168.0.204.57447 > 192.168.0.20.telnet: S 4076228960:407 0.000128 0.000065 IP 192.168.0.20.telnet > 192.168.0.204.57447: S 3718362368:371 0.000159 0.000031 IP 192.168.0.204.57447 > 192.168.0.20.telnet: . ack 1 win 5840 0.000215 0.000056 write length
0.000227 0.000012 IP 192.168.0.204.57447 > 192.168.0.20.telnet: P 1:9(8) ack 1 w 0.000234 0.000007 read length + data
0.000275 0.000041 IP 192.168.0.20.telnet > 192.168.0.204.57447: . ack 9 win 6551 0.002269 0.001994 IP 192.168.0.20.telnet > 192.168.0.204.57447: . 1:5(4) ack 9 w 0.002284 0.000015 IP 192.168.0.204.57447 > 192.168.0.20.telnet: . ack 5 win 5840 0.002300 0.000016 write length
0.002306 0.000006 IP 192.168.0.204.57447 > 192.168.0.20.telnet: P 9:17(8) ack 5 0.002312 0.000006 read length + data
0.002369 0.000057 IP 192.168.0.20.telnet > 192.168.0.204.57447: . ack 17 win 655 0.002568 0.000199 IP 192.168.0.20.telnet > 192.168.0.204.57447: . 5:1465(1460) a 0.002583 0.000015 IP 192.168.0.204.57447 > 192.168.0.20.telnet: . ack 1465 win 8 0.002717 0.000134 IP 192.168.0.20.telnet > 192.168.0.204.57447: . 1465:2925(1460
wireshark
• yum install wireshark-gnome (GUI
つきのをイ ンストールする)• Ethernet
、IP, TCP
のヘッダがどこか色つきで 表示してくれるので便利•
あまり使ったことがないので教えてくださいMACアドレスか らベンダーを調 べて表示する
(らしい)
wireshark
•
複数のTCP
セッションが あってもAnalyze→Follow TCP
Stream
で追跡可能wireshark
wireshark
•
データのダンプもできるwireshark
•
フローグラフtcpflow
• tcpdump
、wireshark
等でキャプチャしたファイ ルからデータフローを取り出すことができる•
同様なソフトウェアは他にもあるビットシフト、マスク
•
ヘッダ情報、データのデコードの際に必要になること がある。•
ビットを節約するため等の理由により、1バイト内に 意味が違うデータが入っている場合にビットシフト、マスク等を使用してデータを取り出すことが必要で ある場合がある。
•
取り出したあとは構造体メンバーに代入するクレートナンバー モジュール ナンバー
多重読み出し
read(sockfd_0, buf_0, sizeof(buf_0));
read(sockfd_1, buf_1, sizeof(buf_1));
read(sockfd_2, buf_2, sizeof(buf_2));
とするとsockfd_0で止まると、sockfd_1が読めるようになっていても プログラムが進行しない。
読めるようになったものをどんどん読むにはselect()あるいはLinuxなら epoll()を使う。
あるいはpthreadを使う。
tcpdump で問題切り分けの例
• MLF
中性子BL 01
での例•
読み出しモジュールはNEUNET
モジュールNEUNET Protocol
PC
(DAQ) NEUNET
モジュールlength request
length + data
length request
length + data
length request
length + data
length data data
読み取り側がまず、読み取りたいデータ長を 指定する。
NEUNETモジュール側では、まず、送ってく
るデータ長を送ってきて続いてデータを送っ てくる。
•
(問題点)読み出しが
Too Much Data
というエラーを出し て止まることがある•
問題切り分けのために正常にデータがきているか
tcpdump
でダンプをとっていただいた正常時のパケットの流れ
DAQPC NEUNETモジュール
TCP Connection Establised
length request
length reply + data reply length request
length reply + TCP SYN
TCP ACK + SYN TCP ACK
BL01 Too Many Data
時のパケット交換図DAQPC NEUNETモジュール
Length request
TCP接続後いきなりデータがきていた (データは 0x47 47 47 47 …)
TCP connection established
( 1) 0.000000 0.000000 IP 192.168.0.2.57446 > 192.168.0.17.telnet: S 982133679:9 82133679(0) win 5840 <mss 1460,nop,nop,sackOK,nop,wscale 8>
( 2) 0.000054 0.000054 IP 192.168.0.17.telnet > 192.168.0.2.57446: S 2302400311:
2302400311(0) ack 982133680 win 1024 <mss 1460>
( 3) 0.000063 0.000009 IP 192.168.0.2.57446 > 192.168.0.17.telnet: . ack 1 win 5 840
( 4) 0.000290 0.000227 IP 192.168.0.17.telnet > 192.168.0.2.57446: . 1:1461(1460 ) ack 1 win 65519
0x0000: 4500 05dc a6fc 4000 8006 ccbb c0a8 0011 E...@...
0x0010: c0a8 0002 0017 e066 893b d738 3a8a 2bb0 ...f.;.8:.+.
0x0020: 5010 ffef 405f 0000 4747 4747 4747 4747 P...@_..GGGGGGGG 0x0030: 4747 4747 4747 4747 4747 4747 4747 4747 GGGGGGGGGGGGGGGG 0x0040: 4747 4747 4747 4747 4747 4747 4747 4747 GGGGGGGGGGGGGGGG 0x0050: 4747 4747 4747 4747 4747 4747 4747 4747 GGGGGGGGGGGGGGGG
BL01
でのダンプの解析:RUN_29 (
その1)
(4)のパケットデータ1460バイト全部0x47 TCP接続完了
TCP+IP Header
BL01
でのダンプの解析:Run_29 (
その2)
( 5) 0.000298 0.000008 IP 192.168.0.2.57446 > 192.168.0.17.telnet: . ack 1461 wi n 8760
0x0000: 4500 0028 d285 4000 4006 e6e6 c0a8 0002 E..(..@.@...
0x0010: c0a8 0011 e066 0017 3a8a 2bb0 893b dcec ...f..:.+..;..
0x0020: 5010 2238 5f58 0000 P."8_X..
( 6) 0.001097 0.000799 IP 192.168.0.2.57446 > 192.168.0.17.telnet: P 1:9(8) ack 1461 win 8760
0x0000: 4500 0030 d286 4000 4006 e6dd c0a8 0002 E..0..@.@...
0x0010: c0a8 0011 e066 0017 3a8a 2bb0 893b dcec ...f..:.+..;..
0x0020: 5018 2238 7c47 0000 a300 0000 0000 4000 P."8|G...@.
( 7) 0.001151 0.000054 IP 192.168.0.17.telnet > 192.168.0.2.57446: . ack 9 win 6 5519
0x0000: 4500 0028 a6fd 4000 8006 d26e c0a8 0011 E..([email protected]....
0x0010: c0a8 0002 0017 e066 893b dcec 3a8a 2bb8 ...f.;..:.+.
0x0020: 5010 ffef 8198 0000 0204 05b4 0a00 P...
Gathererはまずレングスリプライ取得のため4バイト読むがその値 0x47474747==1195853639(超巨大整数)がリクエストした値より 大きかったのでFATAL ERROR 5で停止した(すなわちGathererは 正常に動作していた)。
何がおこったのか?
(FATAL ERROR 5)
length request
BL0
1でのダンプの解析:Run_29 (
その3)
( 8) 0.001367 0.000216 IP 192.168.0.17.telnet > 192.168.0.2.57446: . 1461:2921(1 460) ack 9 win 65519
0x0000: 4500 05dc a6fe 4000 8006 ccb9 c0a8 0011 E...@...
0x0010: c0a8 0002 0017 e066 893b dcec 3a8a 2bb8 ...f.;..:.+.
0x0020: 5010 ffef 57ca 0000 4747 4747 4747 4747 P...W...GGGGGGGG 0x0030: 4747 4747 4747 4747 4747 4747 4747 4747 GGGGGGGGGGGGGGGG 0x0040: 4747 4747 4747 4747 4747 4747 4747 4747 GGGGGGGGGGGGGGGG
0x03e0: 4747 4747 4747 4747 4747 4747 4747 4747 GGGGGGGGGGGGGGGG 0x03f0: 4747 4747 4747 4747 4747 4747 4747 4747 GGGGGGGGGGGGGGGG 0x0400: 4747 4747 0000 01cc 5a13 0dfb 0a22 b43e GGGG....Z....".>
0x0410: 5a13 0f22 0c26 f06b 5a13 0fcc 083a 518f Z..".&.kZ....:Q.
0x0420: 5a13 0ffa 0916 c369 5a13 0fd0 0b0a 8325 Z...iZ...%
0x0430: 5a13 0ff1 0e39 6509 5a13 100c 0f4f c441 Z....9e.Z....O.A 0x0440: 5a13 10fa 0f0f c39c 5a13 1143 0e1e c304 Z...Z..C....
この間全部0x47
途中からそれらしいデータがやってきている。